using System;
using System.IO;
using System.Net.WebSockets;
using System.Threading;
using System.Threading.Tasks;
using GameKit.Core;

namespace GameKitUnity.Network
{
    public class GameSocket : ISocket
    {
        #region ISocket implementation

        public string Name { get; set; }
        public string Address { get; set; }

        public Action OnOpen { get; set; }
        public Action OnClose { get; set; }
        public Action<byte[]> OnBinary { get; set; }
        public Action<Exception> OnError { get; set; }

        #endregion

        private ClientWebSocket _socket;

        public GameSocket(string address)
        {
            Address = address;
        }

        public WebSocketState State => _socket?.State ?? WebSocketState.None;

        public async Task Connect()
        {
            _socket = new ClientWebSocket();
            await _socket.ConnectAsync(new Uri(Address), CancellationToken.None);
            Log.Info($"Socket connected to {Address}");
            OnOpen?.Invoke();
            await ReceiveTask();
        }

        public Task Send(byte[] message)
        {
            return _socket.SendAsync(message, WebSocketMessageType.Binary, true, CancellationToken.None);
        }

        public async void Close()
        {
            if (_socket == null)
            {
                Log.Error("Socket is null");
                return;
            }

            await _socket.CloseAsync(WebSocketCloseStatus.NormalClosure, "Closing", CancellationToken.None);
            Log.Info($"Socket Closed {Address}");
            OnClose?.Invoke();
        }

        private async Task ReceiveTask()
        {
            bool isClosed = false;
            // ArraySegment<T> 是 C# 中一个非常实用的结构体，用来表示数组（Array）的一部分，而不是复制或新建子数组。
            // 它的作用是在不分配新内存的前提下，引用数组的一段连续区域。
            var segment = new ArraySegment<byte>(new byte[8192]);
            var ms = new MemoryStream();
            try
            {
                while (!isClosed)
                {
                    var result = await _socket.ReceiveAsync(segment, CancellationToken.None);

                    if (segment.Array != null)
                    {
                        ms.Write(segment.Array, 0, result.Count);
                        if (!result.EndOfMessage)
                        {
                            continue;
                        }

                        var data = ms.ToArray();
                        ms.SetLength(0);

                        if (result.MessageType == WebSocketMessageType.Close)
                        {
                            isClosed = true;
                            Log.Info($"Socket Closed by Server {Address}");
                        }
                        else if (result.MessageType == WebSocketMessageType.Binary)
                        {
                            OnBinary?.Invoke(data);
                        }
                    }
                }
            }
            catch (Exception e)
            {
                Log.Error(e.Message);
            }
            finally
            {
                ms.Close();
            }
        }
    }
}